/**
 * @file
 */

#include <cstdint>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include "mocha/object_importer.hpp"
#include "mocha/plugin.hpp"
#include "mocha/plugin_factory.hpp"
#include "mocha/shared_library.hpp"

using namespace boost::filesystem;

namespace mocha {
namespace detail {
typedef std::map<const mocha_plugin_factory_params*, plugin_factory*> plugin_factory_con_t;
/**
 * Imports a plugin object using an importer.
 * @param object Plugin object.
 * @param rp Register parameters of the plugin object.
 * @param importer Importer used to adapt the object.
 * @return Imported plugin object. The object may be adapted or may be no different than the input. Never null unless the original (raw) plugin object was null.
 */
void* import_plugin_object(void* object, const mocha_plugin_register_params* rp, const object_importer& importer) {
	void* target = NULL;
	switch (rp->language) {
	case mocha_plugin_language_c:
		target = importer.import(object, rp->destroy);
		break;
	case mocha_plugin_language_cpp:
		target = object;
		break;
	default:
		target = object; // Assume this is a C++ object and do not import (adapt) it.
		break;
	}
	return target;
}
/**
 * Instantiates and imports a plugin object.
 * @param op Object parameters of the plugin object.
 * @param rp Register parameters of the plugin object.
 * @param importer Importer used to adapt the object.
 * @return New (possibly adapted) plugin object. Null if the plugin object could not be instantiated.
 * @see import_plugin_object
 */
void* create_plugin_object(mocha_plugin_object_params* op, const mocha_plugin_register_params* rp, const object_importer& importer) {
	void* source = rp->create(op);
	if (source) {
		return import_plugin_object(source, rp, importer);
	}
	return NULL;
}
/**
 * Clones (copies) a plugin object.
 * @param object Plugin object to clone.
 * @param rp Register parameters of the plugin object to clone.
 * @param importer Importer used to adapt the clone.
 * @return A clone of the original plugin object. Null if the plugin object is not copyable or the copy operation fails.
 */
void* clone_plugin_object(const void* object/*, mocha_plugin_object_params* op*/, const mocha_plugin_register_params* rp, const object_importer& importer) {
	if (!rp->clone) {
		return NULL;
	}
	void* source = rp->clone(object);
	if (source) {
		return import_plugin_object(source, rp, importer);
	}
	return NULL;
}
/**
 * Gets a static mapping of all plugin factories.
 * @return Static plugin factory mapping.
 * @see plugin_factory
 */
plugin_factory_con_t& plugin_factory_map() {
	static plugin_factory_con_t factories;
	return factories;
}
/**
 * Searches the static plugin factory mapping for a factory.
 * @param fp Plugin factory parameters.
 * @return Plugin factory with matching parameters. Note that only pointers are compared, not the contents of the parameters.
 */
plugin_factory* find_plugin_factory(const mocha_plugin_factory_params* fp) {
	plugin_factory_con_t::iterator i = plugin_factory_map().find(fp);
	if (i != plugin_factory_map().end()) {
		return i->second;
	}
	return NULL;
}
/**
 * Adds a plugin factory to the static mapping.
 * @param factory Plugin factory to add.
 */
void add_plugin_factory(plugin_factory* factory) {
	plugin_factory_map().insert(plugin_factory_con_t::value_type(factory->parameters(), factory));
}
/**
 * Removes a plugin factory from the static mapping.
 * @param factory Plugin factory to remove.
 */
void remove_plugin_factory(plugin_factory* factory) {
	plugin_factory_map().erase(factory->parameters());
}
/**
 * Registers a plugin object with a plugin factory.
 * Acts as a hook for C function calls to the core.
 * @param fp Plugin factory parameters.
 * @param type Plugin type name.
 * @param rp Plugin register params.
 * @return Non-zero if an error occurs, otherwise zero.
 * @see mocha_plugin_factory_params
 */
std::int32_t register_plugin_object(mocha_plugin_factory_params* fp, const std::int8_t* type, const mocha_plugin_register_params* rp) {
	plugin_factory* factory = find_plugin_factory(fp);
	if (!factory) {
		return -1;
	}
	return factory->register_object(reinterpret_cast<const char*>(type), rp);
}
/**
 * Invokes a service registered with a plugin factory.
 * Acts as a hook for C function calls to the core.
 * @param fp Plugin factory parameters.
 * @param name Service name.
 * @param params Service parameters. Specific to each service.
 * @return Non-zero if an error occurs, otherwise zero.
 * @see mocha_plugin_factory_params
 */
std::int32_t invoke_plugin_service(mocha_plugin_factory_params* fp, const std::int8_t* name, void* params) {
	plugin_factory* factory = find_plugin_factory(fp);
	if (!factory) {
		return -1;
	}
	return factory->invoke_service(reinterpret_cast<const char*>(name), params);
}
}
plugin_factory::plugin_factory() {
	fp_.version = mocha_plugin_compiled_api_version;
	fp_.register_object = &detail::register_plugin_object;
	fp_.register_service = NULL;
	fp_.invoke_service = &detail::invoke_plugin_service;
	detail::add_plugin_factory(this);
}
plugin_factory::~plugin_factory() {
	detail::remove_plugin_factory(this);
	close();
}
int plugin_factory::open(/*...*/) {
	return -1; // TODO:
}
int plugin_factory::open_xml(const path& file) {
	return -1; // TODO:
}
int plugin_factory::open_binary(const path& file) {
	close();
	lib_ = shared_library::open(file);
	if (!lib_) {
		std::cout << "failed to open shared library \"" << file << "\"\n";
		return -1;
	}
	mocha_plugin_init_func_t* init = reinterpret_cast<mocha_plugin_init_func_t*>(lib_->symbol("mocha_plugin_init_plugin"));
	if (!init) {
		std::cout << "failed to locate initialization symbol\n";
		return -1;
	}
	return init_plugin(init);
}
int plugin_factory::close() {
	int r = deinit_();
	deinit_ = NULL;
	objects_.clear();
	return r;
}
int plugin_factory::register_object(const std::string& type, const mocha_plugin_register_params* rp) {
	if (fp_.version.major != rp->version.major) {
		return -1;
	}
	if (objects_.find(type) != objects_.end()) {
		return -1;
	}
	objects_[type] = *rp;
	return 0;
}
void* plugin_factory::create_object(const std::string& name, const object_importer& importer) const {
	mocha_plugin_object_params op;
	op.type = reinterpret_cast<const int8_t*>(name.c_str());
	op.fp = &fp_;
	object_con_t::const_iterator rp = objects_.find(name);
	if (rp != objects_.end()) {
		return detail::create_plugin_object(&op, &(rp->second), importer);
	}
	return NULL;
}
void* plugin_factory::clone_object(const void* object, const object_importer& importer) const {
	std::string name = reinterpret_cast<const char*>(static_cast<const plugin*>(object)->type_name()); // Extract plugin type name.
	object_con_t::const_iterator rp = objects_.find(name);
	if (rp != objects_.end()) {
		return detail::clone_plugin_object(object, &(rp->second), importer);
	}
	return NULL;
}
int plugin_factory::register_service(/*...*/) {
	return -1;
}
int plugin_factory::invoke_service(const std::string& name, void* params) {
	return -1;
}
int plugin_factory::init_plugin(mocha_plugin_init_func_t init) {
	std::int32_t rc;
	mocha_plugin_deinit_func_t deinit = init(&fp_, &rc);
	if (!deinit) {
		std::cout << "failed to initialize plugin: deinitializer lost\n";
		return -1;
	}
	deinit_ = deinit;
	return 0;
}
}

